package at.ac.tuwien.infosys.aic11.services;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.logging.Logger;

import javax.jws.WebService;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;



import at.ac.tuwien.infosys.aic11.dto.Address;
import at.ac.tuwien.infosys.aic11.dto.CreditRequest;
import at.ac.tuwien.infosys.aic11.dto.Customer;
import at.ac.tuwien.infosys.aic11.dto.DisbursementPreference;
import at.ac.tuwien.infosys.aic11.dto.InternalCustomer;
import at.ac.tuwien.infosys.aic11.dto.Money;
import at.ac.tuwien.infosys.aic11.dto.Offer;
import at.ac.tuwien.infosys.aic11.fault.UnknownAddressFault;
import at.ac.tuwien.infosys.aic11.fault.UnknownCustomerFault;
import at.ac.tuwien.infosys.aic11.fault.UnknownDisbursementPreferenceFault;
import at.ac.tuwien.infosys.aic11.fault.UnknownMoneyFault;
import at.ac.tuwien.infosys.aic11.fault.UnknownOfferFault;
import at.ac.tuwien.infosys.aic11.fault.UnknownRequestFault;
import at.ac.tuwien.infosys.aic11.legacy.ShippingMock;


@WebService(endpointInterface = "at.ac.tuwien.infosys.aic11.services.ShippingService",
serviceName = "ShippingService",targetNamespace = "http://services.aic11.infosys.tuwien.ac.at/")
public class ShippingServiceImpl implements ShippingService {
	
	private ShippingMock sMock=new ShippingMock();
	private final ExceptionListener el =new ExceptionListener();
	
	private static Logger log = Logger.getLogger("ShippingServiceImpl");
	static {
		log.setParent(Logger.getLogger("at.ac.tuwien.infosys.aic11.services"));
	}
	
	


	@Override
	public boolean finaliseContract(CreditRequest cr) throws UnknownRequestFault, UnknownCustomerFault, UnknownDisbursementPreferenceFault, UnknownMoneyFault, UnknownAddressFault, UnknownOfferFault {
		log.info("Call to finaliseContract()");
		
		if(cr==null) {
            log.severe("finaliseContract(): argument of type CreditRequest may not be null");
            UnknownRequestFault flt = new UnknownRequestFault("finaliseContract cannot be called with a null argument");
            log.severe(flt.toString());
            throw flt;
        }
		
		log.info(cr.toString());
		
        if(cr.getCustomer()==null) {
        	log.severe("finaliseContract: argument of type CreditRequest has to contain a Customer object.");
        	UnknownCustomerFault flt = new UnknownCustomerFault("finaliseContract: argument of type CreditRequest has to contain a Customer object.");
            log.severe(flt.toString());
            throw flt;
        }
        if(cr.getCustomer().getDistbursement()==null) {
        	log.severe("finaliseContract: argument of type CreditRequest has to contain a DisbursementPreference object.");
        	UnknownDisbursementPreferenceFault flt = new UnknownDisbursementPreferenceFault("finaliseContract: argument of type CreditRequest has to contain a DisbursementPreference object.");
            log.severe(flt.toString());
            throw flt;
        }
        if(cr.getMoney()==null) {
        	log.severe("finaliseContract: argument of type CreditRequest has to contain a Money object.");
        	UnknownMoneyFault flt = new UnknownMoneyFault("finaliseContract: argument of type CreditRequest has to contain a Money object.");
            log.severe(flt.toString());
            throw flt;
        }
        if(cr.getCustomer().getAddress()==null) {
        	log.severe("finaliseContract: argument of type CreditRequest has to contain an Address object.");
        	UnknownAddressFault flt = new UnknownAddressFault("finaliseContract: argument of type CreditRequest has to contain an Address object.");
            log.severe(flt.toString());
            throw flt;
        }
        if(cr.getOffer()==null) {
        	log.severe("finaliseContract: argument of type CreditRequest has to contain an Offer object.");
        	UnknownOfferFault flt = new UnknownOfferFault("finaliseContract: argument of type CreditRequest has to contain an Offer object.");
            log.severe(flt.toString());
            throw flt;
        }
		
		final InternalCustomer c = cr.getCustomer();
		final DisbursementPreference dp = c.getDistbursement();
		final Money m = cr.getMoney();
		final Address a = c.getAddress();
		final Offer o = cr.getOffer();
	
		/*****************
		 * ship contract *
		 ****************/
		Thread sending =new Thread(
		  new Runnable() {
		   public void run() {
			   shipContract(a,o);
			   log.info("Wait for contract");
			   while(!sMock.gotContractBack()){
				   synchronized (this) {
						try {
							this.wait(1000);
						} catch (InterruptedException e) {
							log.severe(e.toString());
							el.shippingFailed();
						}
				  }
			  }
			   log.info("Contract is back");
		   }
		  }
		);	
		
		/**************
		 * send money *
		 *************/
		Thread disbursement = new Thread(
			new Runnable() {
				public void run() {
					try {
						startDisbursement(dp,c,m);
					} catch (MalformedURLException e) {
						log.severe(e.toString());
						el.disbursementFailed();
					} catch (InvalidParameterException e) {
						log.severe(e.toString());
						el.disbursementFailed();
					} 
					log.info("disbursement finished");
				}
			}
		);
		
		sending.start();
		disbursement.start();	
		
		log.info("waiting for contract and disbursement");
		try {
			sending.join();		
			disbursement.join();
		} catch (InterruptedException e) {
			log.severe(e.toString());
			return false;
		}
		
		if(!el.isContractFinishedSuccessful()||!el.isDisbursementFinishedSuccessful()){
			log.info("Shipping failed, returning error");
			return false;
		}
				
		log.info("Shipping finished successfully");
		return true;
	}
	
	
	@Override
	public void notifyPerMail() {		
		log.info("error while processing credit request. inform customer per mail");
		sMock.sendMail();
	}
	
	private void shipContract(Address a, Offer o){
		log.info("start shipping to "+a.toString());
		sMock.shipContract(a, o);
	}
	
	private void startDisbursement(DisbursementPreference dp,InternalCustomer c, Money m) throws MalformedURLException, InvalidParameterException{
		log.info("Start disbursement:");
		log.info("\tCustomer: " + c.toString());
		log.info("\tPreference: " + dp.getClass().getCanonicalName());
		log.info("\tMoney=" + m.toString());
		QName SERVICE_NAME = new QName("http://services.aic11.infosys.tuwien.ac.at/", "Registry_Service");
		
		URL wsdlURL = new File("registry_service.wsdl").toURI().toURL();
		
		RegistryService ss = new RegistryService(wsdlURL, SERVICE_NAME);
		IRegistryService port = ss.getRegistryService();  

		WsdlEndpoint endpoint=null;	
		endpoint = port.query(dp);
		log.info("Registry service returns: " + endpoint.getLocation());	

		URL wsdlURLdisb=null;
		wsdlURLdisb = new URL(endpoint.getLocation()+"?wsdl");

		QName serviceName = new QName("http://services.aic11.infosys.tuwien.ac.at/", "Disbursement_Service");
		Service service = Service.create(wsdlURLdisb, serviceName);
		
		DisbursementService client = service.getPort(serviceName, DisbursementService.class);

		Customer ec = new Customer(c);
		client.start_money_transfer_process(dp, m, ec);
		log.info("Money sent successfully!");
	}
	
	private class ExceptionListener{
		private boolean disbursementFinishedSuccessful = true;
		private boolean contractFinishedSuccessful = true;
		
		public boolean isDisbursementFinishedSuccessful() {
			return disbursementFinishedSuccessful;
		}
		public void disbursementFailed() {
			this.disbursementFinishedSuccessful =false;
		}
		public boolean isContractFinishedSuccessful() {
			return contractFinishedSuccessful;
		}
		public void shippingFailed() {
			this.contractFinishedSuccessful = false;
		}
	}

	

}
